/**
 * @author Liu Cong
 */

(function(){

const AMFException = FireAMF.Exception;

/**
 * Handle the incoming AMF request by deserializing the data to JavaScript object types
 *
 */
FireAMF.Request = function(){
    /**
     * @var int AMF client type (AMF0, AMF3)
     */
    this._clientType = 0; // default AMF0

    /**
     * @var array Message bodies
     */
    this._bodies = [];

    /**
     * @var array Message headers
     */
    this._headers = [];

    /**
     * @var int Message encoding to use for objects in response
     */
    this._objectEncoding = 0;

    /**
     * @var Zend_Amf_Parse_InputStream
     */
    this._inputStream = undefined;

    /**
     * @var Zend_Amf_Parse_AMF0_Deserializer
     */
    this._deserializer = undefined;

    /**
     * Time of the request
     * @var  mixed
     */
    this._time = undefined;
};

FireAMF.Request.prototype = {
    /**
     * Prepare the AMF InputStream for parsing.
     *
     * @param  string request
     * @return FireAMF.Request
     */
	initialize: function(request){
        this._inputStream  = new FireAMF.BinaryStream(request);
        this._deserializer = new FireAMF.AMF0.Deserializer(this._inputStream);
        this.readMessage(this._inputStream);
        return this;
	},
	
    /**
     * Takes the raw AMF input stream and converts it into valid PHP objects
     *
     * @param  FireAMF.BinaryStream
     * @return FireAMF.Request
     */
    readMessage: function(stream) {
        var clientVersion = stream.readUnsignedShort();
        if ((clientVersion != FireAMF.Constants.AMF0_OBJECT_ENCODING)
            && (clientVersion != FireAMF.Constants.AMF3_OBJECT_ENCODING)
        ) {
            throw new AMFException('Unknown Player Version ' + clientVersion);
        }

        this._bodies  = [];
        this._headers = [];
        var headerCount    = stream.readInt();

        // Iterate through the AMF envelope header
        while (headerCount--) {
            this._headers.push(this.readHeader());
        }

        // Iterate through the AMF envelope body
        var bodyCount = stream.readInt();
        while (bodyCount--) {
            this._bodies.push(this.readBody());
        }

        return this;
    },
	
    /**
     * Deserialize a message header from the input stream.
     *
     * A message header is structured as:
     * - NAME String
     * - MUST UNDERSTAND Boolean
     * - LENGTH Int
     * - DATA Object
     *
     * @return FireAMF.MessageHeader
     */
    readHeader: function() {
        var name     = this._inputStream.readUTF();
        var mustRead = !!this._inputStream.readByte();
        var length   = this._inputStream.readLong();

        try {
            var data = this._deserializer.readTypeMarker();
        } catch (e) {
            throw new AMFException('Unable to parse ' + name + ' header data: ' + e.getMessage() + ' ' + e.getLine());
        }

        var header = new FireAMF.MessageHeader(name, mustRead, data, length);
        return header;
    },
	
    /**
     * Deserialize a message body from the input stream
     *
     * @return FireAMF.MessageBody
     */
    readBody: function() {
        var targetURI   = this._inputStream.readUTF();
        var responseURI = this._inputStream.readUTF();
        var length      = this._inputStream.readLong();

        try {
            var data = this._deserializer.readTypeMarker();
        } catch (e) {
            throw new AMFException('Unable to parse ' + targetURI + ' body data ' + e.getMessage());
        }

        // Check for AMF3 objectEncoding
        if (this._deserializer.getObjectEncoding() == FireAMF.Constants.AMF3_OBJECT_ENCODING) {
            /*
             * When and AMF3 message is sent to the server it is nested inside
             * an AMF0 array called Content. The following code gets the object
             * out of the content array and sets it as the message data.
             */
            if(data && data.prototype == Array && data[0] instanceof FireAMF.Message){
                data = data[0];
            }

            // set the encoding so we return our message in AMF3
            this._objectEncoding = FireAMF.Constants.AMF3_OBJECT_ENCODING;
        }

        var body = new FireAMF.MessageBody(targetURI, responseURI, data);
        return body;
    },

    /**
     * Return an array of the body objects that were found in the amf request.
     *
     * @return array {target, response, length, content}
     */
    getAmfBodies: function() {
        return this._bodies;
    },

    /**
     * Accessor to private array of message bodies.
     *
     * @param  FireAMF.MessageBody message
     * @return FireAMF.Request
     */
    addAmfBody: function(message) {
        this._bodies.push(message);
        return this;
    },

    /**
     * Return an array of headers that were found in the amf request.
     *
     * @return array {operation, mustUnderstand, length, param}
     */
    getAmfHeaders: function() {
        return this._headers;
    },

    /**
     * Return the either 0 or 3 for respect AMF version
     *
     * @return int
     */
    getObjectEncoding: function() {
        return this._objectEncoding;
    },

    /**
     * Set the object response encoding
     *
     * @param  mixed int
     * @return Zend_Amf_Request
     */
    setObjectEncoding: function(encoding) {
        this._objectEncoding = encoding;
        return this;
    },

};

})();
